// The Employee class, implements the Photo Hut employee. The Employee connects to the Work Manager server, asks him for a new job,
// downloads the photo relevant to the job, processes the photo according to the requested effects, and uploads the final photo back to the Work Manager.
#include "../include/Employee.h"

using Poco::Net::StreamSocket;
using Poco::Net::SocketStream;
using Poco::Net::SocketAddress;
using Poco::Exception;

using namespace std;

// Class constructor.
Employee::Employee(std::string host, short port) : port_(port), host_(host) {
	conHandler = new ConnectionHandler(host, port);
}

// Class destructor.
Employee::~Employee(void){
	currentImg.release();
	delete conHandler;
}

// Connect to the remote machine
// Returns false if it was unable to properly connect.
bool Employee::connect() {
	bool ans = true;
	ans = conHandler->connect();
	return ans;
}

// Reconnect to the remote machine
// Returns false if it was unable to properly connect.
bool Employee::reconnect() {
	delete conHandler;
	conHandler = new ConnectionHandler(host_, port_);
	if (!(connect())) {
		return false;
	}
	return true;
}


// Asks the server if there's a new job to work on and downloads it if so.
// Returns false if the connection failed during process.
bool Employee::getNewJob() {
	cout << "The Employee is trying to get a new job" << endl;
	string message;
	stringstream messageStrm;
	// Concatenate the needed arguments to the employee's request.
	messageStrm << "POST /jobs/get-new-job HTTP/1.1\r\n" << "Host: " << host_ << "\r\n";
	message = messageStrm.str();
	string answer;
	int contentLength;
	asciiToUtf8 (message); // Encode the message from Ascii to UTF-8.
	if (!send(message)) { // Send the request to the server
		cout << "Disconnected. Exiting...\n" << endl;
		return false;
	}
	cout << "Employee: " << message << endl;
	if (!getOneLine(answer)) { // Read a line from the server's reply.
		cout << "Disconnected. Exiting...\n" << endl;
		return false;
	}
	cout << "Server: " << answer << endl;
	while (answer.substr(9,3) == "204") { // As long as the server successfully processed the request, but doesn't have a job ready...
		cout << "Job not ready yet - The employee is going to sleep for 30 seconds..." << endl;
		Poco::Thread::sleep(30000);
		if (!(reconnect())) { // Reconnect to the server
			return false;
		}
		if (!send(message)) { // Send a new job request.
			cout << "Disconnected. Exiting...\n" << endl;
			return false;
		}
		cout << "Employee: " << message << endl;
		if (!getOneLine(answer)) { 	// Read a line from the server's reply.
			cout << "Disconnected. Exiting...\n" << endl;
			return false;
		}
		cout << "Server: " << answer << endl;
	}
	if (answer.substr(9,3) != "200") { // In case the server sent an invalid code...
		cout << "ERROR - 200 or 204 server response expected! Exiting...";
		return false;
	}
	else { // In case the server sent a 200 "OK" code.
		while (answer.find("Content-Length:") == string::npos) { // Read from stream until the "Content-Length" header.
			if (answer.find("resource:") != string::npos) { // Checks if the current line (header) has the job's resource, and takes it if so.
				istringstream (answer.substr(10)) >> resource;
			}
			if (!getOneLine(answer)) { // Read a line from the server's reply.
				cout << "Disconnected. Exiting...\n" << endl;
				return false;
			}
			cout << "Server: " << answer << endl;
		}
		istringstream (answer.substr(16)) >> contentLength; // Take the content's length from the current header.
		cout << "The Content Length is:" << contentLength << endl;
		if (!getOneLine(answer)) { // Read a line from the server's reply.
			cout << "Disconnected. Exiting...\n" << endl;
			return false;
		}
		cout << "Server: " << answer << endl;
		char jobXML[contentLength];
		if (!getBytes(jobXML, contentLength)) { // Reads and holds data-size number of bytes from the stream.
			cout << "Disconnected. Exiting...\n" << endl;
			return false;
		}
		jobXML[contentLength] = '\0'; // Sets the last char in the array as 'null'.
		xml.assign ( jobXML, jobXML+contentLength ); // Puts the data in a string to make it ready for parsing.
	}
	return true;
}


// Downloads the given job's image.
// Returns false if the connection failed during process.
bool Employee::getImg(Job& job) {
	if (!(reconnect())) { // Reconnect to the server
		return false;
	}
	cout << "The Employee is getting the job's photo" << endl;
	string message;
	string answer;
	stringstream messageStrm;
	// Concatenate the needed arguments to the employee's request.
	messageStrm << "GET /photos/" << resource <<"?rep=" << job.getInput() << " HTTP/1.1\r\nHost: " << host_ << "\r\n";
	message = messageStrm.str();
	asciiToUtf8 (message);
	if (!send(message)) { // Send the request to the server
		cout << "Disconnected. Exiting...\n" << endl;
		return false;
	}
	cout << "Employee: " << message << endl;
	if (!getOneLine(answer)) { // Read a line from the server's reply.
		cout << "Disconnected. Exiting...\n" << endl;
		return false;
	}

	while (answer.substr(9,3) == "206") { // As long as the server successfully processed the request, but doesn't have a photo ready...
		cout << "Photo not ready yet - The employee is going to sleep for 30 seconds..." << endl;
		Poco::Thread::sleep(30000);
		if (!(reconnect())) { // Reconnect to the server
			return false;
		}
		if (!send(message)) { // Send a new job request.
			cout << "Disconnected. Exiting...\n" << endl;
			return false;
		}
		cout << "Employee: " << message << endl;
		if (!getOneLine(answer)) { 	// Read a line from the server's reply.
			cout << "Disconnected. Exiting...\n" << endl;
			return false;
		}
		cout << "Server: " << answer << endl;
	}
	cout << "Server: " << answer << endl;
	if (answer.substr(9,3) == "404") {
		cout << "ERROR - 404 server response! There is not such photo! Exiting...";
		return false;
	}

	else if (answer.substr(9,3) != "200") { // in case the server sent an invalid code.
		cout << "ERROR - 200, 206 or 404 server response expected! - ! Exiting...";
		return false;
	}

	while (answer.find("Content-Length:") == string::npos) { // Read from stream until the "Content-Length" header.
		if (answer.find("Content-Type:") != string::npos) { // Checks if the current line (header) has the image's content-type, and takes it if so.
			contentType = answer.substr(14);
		}
		if (!getOneLine(answer)) { // Read a line from the server's reply.
			cout << "Disconnected. Exiting...\n" << endl;
			return false;
		}
		cout << "Server: " << answer << endl;
	}
	istringstream (answer.substr(16)) >> contentLength;
	if (!getOneLine(answer)) { // Read a line from the server's reply.
		cout << "Disconnected. Exiting...\n" << endl;
		return false;
	}
	cout << "Server: " << answer << endl;
	char imgBytes[contentLength];
	if (!getBytes(imgBytes, contentLength)) { // Reads and holds data-size number of bytes from the stream.
		cout << "Disconnected. Exiting...\n" << endl;
		return false;
	};
	vector<unsigned char> buf;
	buf.assign(imgBytes, imgBytes+contentLength); // Puts the image in a buffer.
	currentImg = imdecode((Mat)buf, -1); // converting the buffer to an image.
	return true;
}


// Uploads the given job's image to the server.
// Returns false if the connection failed during process.
bool Employee::sendImg(Job& job) {
	if (!(reconnect())) {
		return false;
	}
	cout << "The Employee is sending the processed photo back to the server" << endl;
	string message;
	string answer;
	stringstream messageStrm;
	// Concatenate the needed arguments to the employee's request.
	messageStrm << "PUT /photos/" << resource << "?rep=" << job.getOutput() << " HTTP/1.1" << "\r\nHost: " << host_ << "\r\n" <<"Content-Type: " << contentType << "\r\nContent-Length: " <<  contentLength << "\r\n";
	message = messageStrm.str();
	asciiToUtf8 (message); // Convert the employee's message encoding from Ascii to UTF-8ץ
	vector<unsigned char> buf;
	string type = ".";
	type += contentType.substr(6);
	imencode(type, currentImg, buf); // Puts the image in a buffer.
	if (!send(message)) { // Send the request to the server
		cout << "Disconnected. Exiting...\n" << endl;
		return false;
	}
	if (!(reconnect())) { // Reconnect to the server
		return false;
	}
	cout << "Employee: " << message << endl;
	char* imageToSend = reinterpret_cast<char*> (&buf[0]);
	if (!sendBytes(imageToSend,contentLength)) { // Send the image to the server
		cout << "Disconnected. Exiting...\n" << endl;
		return false;
	}
	return true;
}


// Read one line from the server's reply and put it in a string.
// Returns false in case connection closed before a newline can be read.
bool Employee::getOneLine (string& frame) {
	frame = "";
	char ch = '\0';
	try {
		conHandler->sock_.receiveBytes(&ch, 1);
		while ('\n' != ch) { // Read and append the read characters until end of line.
			frame.append(1, ch);
			conHandler->sock_.receiveBytes(&ch, 1);
		}
	} catch (Poco::Exception error) {
		cout << "recv failed (Error: " << error.displayText() << ')' << endl;
		return false;
	}
	utf8ToAscii(frame); // Convert the read line from UTF-8 encoding to Ascii.
	return true;
}


// Converts the given message from UTF-8 encoding to Ascii.
void Employee::utf8ToAscii (string& message) {
	Poco::UTF8Encoding utf8Encoding;
	Encoder* encoder = new Encoder(utf8Encoding);
	encoder->utf8ToAscii(message);
	delete encoder;
}


// Converts the given message from Ascii encoding to UTF-8.
void Employee::asciiToUtf8 (string& message) {
	Poco::ASCIIEncoding asciiEncoding;
	Encoder* encoder = new Encoder(asciiEncoding);
	encoder->asciiToUtf8(message);
	delete encoder;
}


// Read a fixed number of bytes from the server - blocking.
// Returns false in case the connection is closed before bytesToRead bytes can be read.
bool Employee::getBytes(char frame[], int bytesToRead) {
	int tmp = 0;
	try {
		while ( bytesToRead > tmp ) { // Stop when bytesToRead number of bytes were read.
			tmp += conHandler->sock_.receiveBytes(frame + tmp, bytesToRead - tmp);
		}
	} catch (Poco::Exception error) {
		cout << "recv failed (Error: " << error.displayText() << ')' << endl;
		return false;
	}
	return true;
}


// Process the job's requested effects.
void Employee::processJob(Job& job) {
	job.processAllEffects(currentImg);
}


// Send data to the remote host through the socket stream
// Returns false if the connection failed during process.
bool Employee::send(const string& message) {
	try {
		conHandler->stream_ << message << endl << flush;
	} catch (Poco::Exception error) {
		cout << "Error while sending to a socket (Error:" << error.displayText() << ')' << endl;
		return false;
	}
	return true;
}


// Send a fixed number of bytes from the client - blocking.
// Returns false in case the connection is closed before bytesToWrite bytes can be read.
bool Employee::sendBytes(const char frame[], int bytesToWrite) {
	int tmp = 0;
	try {
		while ( bytesToWrite > tmp ) { // Stop when bytesToWrite number of bytes were read.
			tmp += conHandler->sock_.sendBytes(frame + tmp, bytesToWrite - tmp);
		}
	} catch (Poco::Exception error) {
		cout << "recv failed (Error: " << error.displayText() << ')' << endl;
		return false;
	}
	return true;
}


// Returns the job's xml in string format.
string Employee::getXML() {
	return xml;
}
